/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor.ext; import java.io.Writer; import java.io.IOException; import javax.swing.text.Segment; import org.netbeans.editor.Acceptor; import org.netbeans.editor.Analyzer; import org.netbeans.editor.EditorDebug; import org.netbeans.editor.BaseKit; import org.netbeans.editor.Settings; import org.netbeans.editor.SettingsUtil; import org.netbeans.editor.DefaultSettings; import org.netbeans.editor.Syntax; /** * Base format writer used to format the text. * * @author Miloslav Metelka * @version 1.00 */ public class BaseFormatWriter extends Writer { /** Formatter reference */ private BaseFormatter formatter; /** Syntax scanner used to process the text */ private Syntax syntax; private boolean syntaxInited; /** Underlying writer */ protected Writer underWriter; /** Start indentation on which all additional indentation * is based. */ protected int startIndent; /** Whether the starting position is at the begining * of the first input line. */ protected boolean startAtLineBegin; /** Whether the input is currently at the begin of line */ protected boolean atLineBegin; /** Whether the current token is part * of the initial white-space of the line */ protected boolean initialWhitespace; /** Ignore initial whitespace on the line. Process token * is not called for the tokens containing initial whitespace * on the line. */ protected boolean ignoreInitWS; /** Whether the writer is currently processing the first * line of the input characters. There can be some special handling * for the first line. */ protected boolean firstInputLine; /** Segment with the scanned text. Its offset must be kept at zero. */ protected String inputString; /** Segment for the current line */ protected StringBuffer lineBuffer; /** Output segment. The current line begins at seg.offset and * its length is seg.length. */ protected StringBuffer outBuffer; /** Indent for the current line. When the line is written * to the output. At the startup this is populated by startIndent. */ protected int indent; /** Indent of the next line. After the line is written, * indent variable is populated by the value of this variable. */ protected int nextLineIndent; /** When there was the explicit flush() requested on this writer. */ protected boolean flushedEarly; /** Trim trailing whitespace from each output line */ protected boolean outRightTrimLine; /** First token on the line (right after the previous new-line) */ protected boolean firstTokenOnLine; public BaseFormatWriter(BaseFormatter formatter, Syntax syntax, Writer underWriter, int startIndent, boolean startAtLineBegin) { this.formatter = formatter; this.underWriter = underWriter; this.startIndent = startIndent; this.syntax = syntax; this.startAtLineBegin = startAtLineBegin; atLineBegin = startAtLineBegin; firstInputLine = true; lineBuffer = new StringBuffer(); outBuffer = new StringBuffer(); indent = formatter.roundIndent(startIndent); // keep the real original indent nextLineIndent = indent; initialWhitespace = startAtLineBegin; firstTokenOnLine = startAtLineBegin; if (!startAtLineBegin) { indent = 0; } outRightTrimLine = true; // debugMode = Integer.MAX_VALUE; } public void write(char buf[], int off, int len) throws IOException { if ((debugMode & DEBUG_INPUT_STRING) != 0) { System.out.println("Input off=" + off + ", len=" + len // NOI18N + ", text='" + EditorDebug.debugChars(buf, off, len) + "'"); // NOI18N } if (len == 0) { return; } int preScan = syntax.getPreScan(); char[] newBuf = new char[preScan + len]; if (preScan > 0) { int inpLen = inputString.length(); int preScanOffset = inpLen - preScan; inputString.getChars(preScanOffset, inpLen, newBuf, 0); } System.arraycopy(buf, off, newBuf, preScan, len); buf = newBuf; inputString = new String(buf, 0, buf.length); syntax.relocate(buf, preScan, len, false); boolean done = false; while (!done) { int tokenID = syntax.nextToken(); int helperID = syntax.getHelperID(); switch (tokenID) { case Syntax.EOT: // end of buffer text reached done = true; break; case Syntax.EOL: // end of line in scanned text if (processEOL()) { finishLine(); } initialWhitespace = true; firstTokenOnLine = true; break; default: int tokenOffset = syntax.getTokenOffset(); int tokenLen = syntax.getTokenLength(); String token = inputString.substring(tokenOffset, tokenOffset + tokenLen); if (initialWhitespace) { initialWhitespace = isWhitespaceToken(tokenID, helperID, token); } if (!initialWhitespace || !ignoreInitWS) { lineBuffer.append(processToken(tokenID, helperID, token)); } firstTokenOnLine = false; break; } } } /** Process the current token. */ protected String processToken(int tokenID, int helperID, String token) { return token; } protected boolean isWhitespaceToken(int tokenID, int helperID, String token) { for (int i = token.length() - 1; i >= 0; i--) { if (!Character.isWhitespace(token.charAt(i))) { return false; } } return true; } /** Process end of line */ protected boolean processEOL() { return true; } protected void changeIndent(int relIndent) { if ((debugMode & DEBUG_INDENT) != 0) { System.out.println("Changing indent of the current line from " // NOI18N + indent + " to " + (indent + relIndent)); // NOI18N } indent += relIndent; } protected void changeNextLineIndent(int relNextIndent) { if ((debugMode & DEBUG_INDENT) != 0) { System.out.println("Changing indent of the next line from " // NOI18N + indent + " to " + (nextLineIndent + relNextIndent)); // NOI18N } nextLineIndent += relNextIndent; } protected char[] getIndentChars() { return Analyzer.getIndentChars(Math.max(indent, 0), formatter.expandTabs(), formatter.getTabSize()); } protected boolean isEndLineWhitespace(char ch) { return Character.isWhitespace(ch); } /** Used to flush the current line into output buffer * @param flushingEarly whether the line is being flushed before it was fully * read from the input stream */ protected void writeLine(boolean flushingEarly) { int dbgTrimLen = 0; if (outRightTrimLine) { // trim trailing whitespace int endOffset = lineBuffer.length(); if (endOffset > 0 && lineBuffer.charAt(endOffset - 1) == '\n') { endOffset--; } int endWSOffset = endOffset; while (endWSOffset > 0) { if (isEndLineWhitespace(lineBuffer.charAt(endWSOffset - 1))) { endWSOffset--; } else { break; } } dbgTrimLen = endOffset - endWSOffset; if (dbgTrimLen > 0) { lineBuffer.delete(endWSOffset, endOffset); } } char[] indentChars = null; boolean emptyLine // is the currently written line empty? = (lineBuffer.length() == 0) // it is if there are no characters on the line || (lineBuffer.length() == 1 && lineBuffer.charAt(0) == '\n'); // or just new-line if (emptyLine && !flushedEarly && flushingEarly) { // the line wasn't flushed early, but now we want to flush it early // we don't know whether there will be any other characters but we suppose so // in this case, so we'll write the indentation // This is because of NetBeans code generator needs to know the positions // in the document so it first calls flush() with no generated code // and it expects to get the correct indent as an output if (firstInputLine) { // enable this behavior only on the first input line // on the later lines the flushing adds the indent too early and // if there's a '}' the indent is not decreased emptyLine = false; } } if (!emptyLine && (!firstInputLine || startAtLineBegin) && !flushedEarly ) { // write indent indentChars = getIndentChars(); outBuffer.append(indentChars); } if ((debugMode & DEBUG_FORMAT) != 0) { StringBuffer sb = new StringBuffer(); sb.append("Creating output text: "); // NOI18N if (emptyLine) { sb.append("Empty Line"); // NOI18N } if (dbgTrimLen > 0) { if (emptyLine) { sb.append(", "); // NOI18N } sb.append("Right trim="); // NOI18N sb.append(dbgTrimLen); } if (indentChars != null && indentChars.length > 0) { if (emptyLine || dbgTrimLen > 0) { sb.append(", "); // NOI18N } sb.append("Indentation="); // NOI18N sb.append(indentChars.length); } sb.append(" Text='"); // NOI18N sb.append(EditorDebug.debugString(lineBuffer.toString())); sb.append("', Length="); // NOI18N sb.append(lineBuffer.length()); System.out.println(sb.toString()); } if (atLineBegin && (indentChars != null || lineBuffer.length() > 0)) { atLineBegin = false; } outBuffer.append(lineBuffer); lineBuffer.setLength(0); } /** This method is called when there's an new-line found * in the input string. All the tokens on the line were * completed and the line is ready to be written out. * It first appends new-line to the end of line * then it writes it to the output. * Start new-line. */ protected void finishLine() { if ((debugMode & DEBUG_LINE) != 0) { System.out.println("Finishing line ... Writing line to output ..."); // NOI18N } lineBuffer.append("\n"); // NOI18N writeLine(false); firstInputLine = false; flushedEarly = false; indent = nextLineIndent; atLineBegin = true; } protected void flushLine() { if ((debugMode & DEBUG_LINE) != 0) { System.out.println("Flushing line ... Writing line to output ..."); // NOI18N } writeLine(true); if (!atLineBegin) { if ((debugMode & DEBUG_LINE) != 0) { System.out.println("Line flushed early"); // NOI18N } flushedEarly = true; } } public void flush() throws IOException { if ((debugMode & DEBUG_FLUSH_CLOSE) != 0) { System.out.println("flush() requested"); // NOI18N } int preScan = syntax.getPreScan(); if (preScan > 0) { // some chars at end of buffer String preScanStr = inputString.substring(inputString.length() - preScan); lineBuffer.append(preScanStr); syntax.reset(); } flushLine(); if ((debugMode & DEBUG_OUTPUT_STRING) != 0) { System.out.println("Output text='" + EditorDebug.debugString(outBuffer.toString()) + "'"); // NOI18N } int outLen = outBuffer.length(); if (outLen > 0) { char[] buf = new char[outLen]; outBuffer.getChars(0, outLen, buf, 0); underWriter.write(buf, 0, outLen); outBuffer.setLength(0); } underWriter.flush(); } public void close() throws IOException { if ((debugMode & DEBUG_FLUSH_CLOSE) != 0) { System.out.println("close() requested"); // NOI18N } flush(); underWriter.close(); } // Debugging public int debugMode; // debugging of the formatter actions /** Debug formatting of the text */ public static final int DEBUG_FORMAT = 2; /** Debug flushes and closing of the buffer */ public static final int DEBUG_FLUSH_CLOSE = 4; /** Debug the indentation changes */ public static final int DEBUG_INDENT = 8; /** Debug the whole input string */ public static final int DEBUG_INPUT_STRING = 16; /** Debug the whole output (written to underWriter) string */ public static final int DEBUG_OUTPUT_STRING = 32; /** Debug the process of creating the output line */ public static final int DEBUG_LINE = 64; /** Debug the input tokens */ public static final int DEBUG_TOKEN = 128; } /* * Log * 12 Gandalf 1.11 1/18/00 Miloslav Metelka * 11 Gandalf 1.10 1/13/00 Miloslav Metelka Localization * 10 Gandalf 1.9 1/7/00 Miloslav Metelka * 9 Gandalf 1.8 1/6/00 Miloslav Metelka * 8 Gandalf 1.7 12/28/99 Miloslav Metelka * 7 Gandalf 1.6 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 6 Gandalf 1.5 9/15/99 Miloslav Metelka * 5 Gandalf 1.4 9/10/99 Miloslav Metelka * 4 Gandalf 1.3 8/27/99 Miloslav Metelka * 3 Gandalf 1.2 7/21/99 Miloslav Metelka * 2 Gandalf 1.1 7/20/99 Miloslav Metelka * 1 Gandalf 1.0 7/9/99 Miloslav Metelka * $ */